package io.gitee.chearnee.fw.common.utils;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

@Slf4j
public final class CompletableFutureUtil {

    private final List<CompletableFuture<?>> taskFuture;

    public CompletableFutureUtil(int size) {
        taskFuture = new ArrayList<>(size);
    }

    public static void runAsync(Executor executor, Runnable... runnables) {
        BiFunction<Executor, Runnable, CompletableFuture<Void>> futureBiFunction = (Executor e, Runnable r) -> {
            if (e == null) {
                return CompletableFuture.runAsync(r);
            }
            return CompletableFuture.runAsync(r, e);
        };
        CompletableFutureUtil completableFutureUtil = new CompletableFutureUtil(runnables.length);
        for (Runnable runnable : runnables) {
            completableFutureUtil.taskFuture.add(futureBiFunction.apply(executor, runnable));
        }
        CompletableFuture.allOf(completableFutureUtil.taskFuture.toArray(new CompletableFuture[0])).join();
    }

    public static void runAsync(Runnable... runnables) {
        runAsync(null, runnables);
    }

    @SafeVarargs
    @SneakyThrows
    public static List<Object> supplyAsync(Executor executor, Supplier<Object>... suppliers) {
        CompletableFutureUtil completableFutureUtil = new CompletableFutureUtil(suppliers.length);
        BiFunction<Executor, Supplier<Object>, CompletableFuture<Object>> futureBiFunction = (Executor e, Supplier<Object> r) -> {
            CompletableFuture<Object> completableFuture;
            if (e == null) {
                completableFuture = CompletableFuture.supplyAsync(r);
            } else {
                completableFuture = CompletableFuture.supplyAsync(r, e);
            }
            return completableFuture.exceptionally(throwable -> {
                log.error(ThrowableUtil.toString(throwable));
                return null;
            });
        };
        for (Supplier<Object> runnable : suppliers) {
            completableFutureUtil.taskFuture.add(futureBiFunction.apply(executor, runnable));
        }
        CompletableFuture.allOf(completableFutureUtil.taskFuture.toArray(new CompletableFuture[0])).join();
        List<Object> result = new ArrayList<>(completableFutureUtil.taskFuture.size());
        for (CompletableFuture<?> completableFuture : completableFutureUtil.taskFuture) {
            result.add(completableFuture.get());
        }
        return result;
    }

    @SafeVarargs
    public static List<Object> supplyAsync(Supplier<Object>... runnables) {
        return supplyAsync(null, runnables);
    }

    public static <T> void runAsync(Collection<T> dataList, Consumer<T> consumer) {
        runAsync(null, dataList, consumer);
    }

    public static <T> void runAsync(Executor executor, Collection<T> dataList, Consumer<T> consumer) {
        List<Runnable> runnableList = new ArrayList<>(dataList.size());
        for (T t : dataList) {
            runnableList.add(() -> consumer.accept(t));
        }
        runAsync(executor, runnableList.toArray(new Runnable[0]));
    }

    public static <T, R> List<R> supplyAsync(Collection<T> data, Function<T, R> function) {
        return supplyAsync(null, data, function);
    }

    @SuppressWarnings("unchecked")
    public static <T, R> List<R> supplyAsync(Executor executor, Collection<T> data, Function<T, R> function) {
        List<Supplier<R>> suppliers = new ArrayList<>();
        for (T t : data) {
            suppliers.add(() -> function.apply(t));
        }
        return supplyAsync(executor, suppliers.toArray(new Supplier[0]));
    }

    public static void main(String[] args) {
        List<Object> objects = supplyAsync(() -> {
            int a = 1 / 0;
            return 1;
        }, () -> {
            return 2;
        });
        System.out.println(Arrays.toString(objects.toArray()));
    }

//    @FunctionalInterface
//    interface  FutureRunnable{
//        <E,T extends Executor,R extends Runnable> R run(E e,T t);
//    }
}
